/***************************************************************************
 *   Copyleft 2008-2009 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./vMaterialParser.h"

using namespace std;
namespace Vendetta {
	vMaterialParser::vMaterialParser() {
		mStates<<VLS_DEFAULT;
		mpTempMaterial = vNull;
		mpPass = vNull;
		mpTextureUnit = vNull;
		mLineInFile = 0;
	}
	
	vMaterialParser::~vMaterialParser() {
		if (mpTempMaterial) {
			delete mpTempMaterial;
		}
	}

	vBool vMaterialParser::LoadFromFile(const vString& fileName) {
		mStates.Clear();
		mStates<<VLS_DEFAULT;
		mpTempMaterial = vNull;
		mpPass = vNull;
		mpTextureUnit = vNull;

		mLineInFile = 0;
		vBool bLoad = false;

		try {
			bLoad = vTxtInputStream::LoadFromFile(fileName);
		} catch (vException& e) {
			if (mpTempMaterial) {
				delete mpTempMaterial;
				mpTempMaterial = vNull;
			}
			throw e;
		} catch (vFatalException& e) {
			if (mpTempMaterial) {
				delete mpTempMaterial;
				mpTempMaterial = vNull;
			}
			throw e;
		}

		return bLoad;
	}

	vVoid vMaterialParser::postLineLoad(const vString& fileName, const vString& strLine) {
		vString ret = vStringTokenizer::EraseDelimeter(vStringTokenizer::EraseDelimeter(strLine, " "), "\t");
		vUInt uiPos = ret.Find("//");
		if (uiPos != vString::MaxPos) {
			ret = ret.SubString(0, uiPos);
		}
		if (ret.IsEmpty()) {
			return;
		}
		//cout<<ret<<endl;

		vArray<vString> vals = vStringTokenizer::Split(ret, ":");
		mLineInFile++;
		if (vals.IsEmpty() || vals[0].ToLowerCase().IsEqual("//")) {
			return;
		}
		vString args0Lower = vals[0].ToLowerCase();

		if (mStates.Top() == VLS_DEFAULT && args0Lower.IsEqual("material")) {
			parseMaterial(fileName, vals);
		} else if (args0Lower.IsEqual("{")) {
			if (!changeOpenState(mStates.Top())) {
				throw vFatalException("Wrong material file: \"%s\" format at line: %i", 
					fileName.ToConstCharPtr(), mLineInFile);
			}
		} else if (args0Lower.IsEqual("}")) {
			if (!changeCloseState(mStates.Top())) {
				throw vFatalException("Wrong material file: \"%s\" format at line: %i", 
					fileName.ToConstCharPtr(), mLineInFile);
			}
		} else if (args0Lower.IsEqual("pass") && mStates.Top() == VLS_MATERIAL_IN) {
			parsePass(fileName, vals);
		} else if (args0Lower.IsEqual("textureunit") && mStates.Top() == VLS_PASS_IN) {
			parseTextureUnit(fileName, vals);
		} else if (mStates.Top() == VLS_PASS_IN) {
			parsePassIn(fileName, vals);
		} else if (mStates.Top() == VLS_TEXTURE_IN) {
			parseTextureUnitIn(fileName, vals);
		} else {
			throw vFatalException("Wrong material file format: \"%s\" at line: %i", 
					fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vVoid vMaterialParser::parseMaterial(const vString& fileName, const vArray<vString>& args) {
		vInt iIndex = -1;
		vUInt uiLength;
		
		try {
			if (args.Size() >= 2) {
				if (vString("").IsEqual(args[1])) {
					throw vException();
				}
				uiLength = args[1].Length();
				if (args[1].SubString(uiLength - 1, uiLength).IsEqual("{")) {
					uiLength--;
					mStates<<VLS_MATERIAL_IN;
				} else {
					mStates<<VLS_MATERIAL_OPEN_BRACKET;
				}
				mpTempMaterial = new vMaterial(args[1].SubString(0, uiLength));				
			} else {
				throw vException();
			}
		} catch (vException&) {
			throw vFatalException("Wrong material file syntax, \"Material\" element should be \"Material: someName\" in \"%s\" at line: %i.", 
				fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vVoid vMaterialParser::parsePass(const vString& fileName, const vArray<vString>& args) {
		try {
			if (args.Size() >= 1) {
				if (args.Size() == 2 && vString("{").IsEqual(args[1])) {
					mStates<<VLS_PASS_IN;					
				} else {
					mStates<<VLS_PASS_OPEN_BRACKET;
				}
				mpPass = mpTempMaterial->createPass();
				mpTempMaterial->addPass(mpPass);
				//TODO add passess to materials			
			} else {
				throw vException();
			}
		} catch (vException&) {
			throw vFatalException("Wrong material file syntax, \"Pass\" element should be \"Pass: someName\" in \"%s\" at line: %i.", 
				fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vVoid vMaterialParser::parsePassIn(const vString& fileName, const vArray<vString>& args) {
		try {
			if (args.Size() == 2) {
				vString vName = vString(args[0]).ToLowerCase();
				if (vString("").IsEqual(args[1])) {
					throw vException();
				}
				if (vString("lighting").IsEqual(vName)) {
					mpPass->SetLighting(parseBoolParam(fileName, args[1]));
				} else if (vString("ambient").IsEqual(vName)) {
					mpPass->SetAmbient(parseLightComponent(fileName, args[1]));
				} else if (vString("diffuse").IsEqual(vName)) {
					mpPass->SetDiffuse(parseLightComponent(fileName, args[1]));
				} else if (vString("specular").IsEqual(vName)) {
					mpPass->SetSpecular(parseLightComponent(fileName, args[1]), parseSpecularFactor(fileName, args[1]));
				} else if (vString("emissive").IsEqual(vName)) {
					mpPass->SetEmissive(parseLightComponent(fileName, args[1]));
				} else {
					throw vException();
				}	
			} else {
				throw vException();
			}
		} catch (vException&) {
			throw vFatalException("Wrong material file syntax, \"Pass\" element should have valid params. Error in \"%s\" at line: %i.", 
				fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vVoid vMaterialParser::parseTextureUnit(const vString& fileName, const vArray<vString>& args) {
		try {
			if (args.Size() >= 1) {
				if (args.Size() == 2 && vString("{").IsEqual(args[1])) {
					mStates<<VLS_TEXTURE_IN;
				} else {
					mStates<<VLS_TEXTURE_OPEN_BRACKET;
				}
				mpTextureUnit = mpPass->createTextureUnit();
				mpPass->addTextureUnit(mpTextureUnit);
			} else {
				throw vException();
			}
		} catch (vException&) {
			throw vFatalException("Wrong material file syntax, \"Texture2D\" element should be \"Texture2D: someName\" in \"%s\" at line: %i.", 
				fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vVoid vMaterialParser::parseTextureUnitIn(const vString& fileName, const vArray<vString>& args) {
		//TODO add sth to wrap texture params
		try {
			if (args.Size() >= 2) {
				vString vName = vString(args[0]).ToLowerCase();
				if (vString("").IsEqual(args[1])) {
					throw vException();
				}
				if (vString("blending").IsEqual(vName)) {
					mpTextureUnit->SetBlending(parseBoolParam(fileName, args[1]));
				} else if (vString("addressmode").IsEqual(vName)) {
					vArray<vString> params = vStringTokenizer::Split(args[1], ",");
					if (params.Size() == 1) {
						mpTextureUnit->SetUVWAddressMode(parseAddressMode(fileName, args[1]));
					} else if (params.Size() == 2) {
						mpTextureUnit->SetUAddressMode(parseAddressMode(fileName, params[0]));
						mpTextureUnit->SetVAddressMode(parseAddressMode(fileName, params[1]));
					} else if (params.Size() == 3) {
						mpTextureUnit->SetUAddressMode(parseAddressMode(fileName, params[0]));
						mpTextureUnit->SetVAddressMode(parseAddressMode(fileName, params[1]));
						mpTextureUnit->SetWAddressMode(parseAddressMode(fileName, params[2]));
					} else {
						throw vException();
					}
				} else if (vString("blendmode").IsEqual(vName)) {
					vArray<vString> params = vStringTokenizer::Split(args[1], ",");
					if (params.Size() == 2) {
						mpTextureUnit->SetSrcBlendMode(parseBlendMode(fileName, params[0]));
						mpTextureUnit->SetDstBlendMode(parseBlendMode(fileName, params[1]));
					} else {
						throw vException();
					}
				} else if (vString("filtermode").IsEqual(vName)) {
					mpTextureUnit->SetMinMagFilterMode(parseFilterMode(fileName, args[1]));
				} else if (vString("rotate").IsEqual(vName)) {
					mpTextureUnit->SetRotate(parseRealParam(fileName, args[1]));
				} else if (vString("scale").IsEqual(vName)) {
					vArray<vString> params = vStringTokenizer::Split(args[1], ",");
					if (params.Size() == 1) {
						mpTextureUnit->SetXYScale(parseRealParam(fileName, args[1]));
					} else if (params.Size() == 2) {
						mpTextureUnit->SetXScale(parseRealParam(fileName, params[0]));
						mpTextureUnit->SetYScale(parseRealParam(fileName, params[1]));
					} else {
						throw vException();
					}
				} else if (vString("texture2d").IsEqual(vName)) {
					//todo valid if textures exist
				} else {
					throw vException();
				}
				//TODO add passess to materials			
			} else {
				throw vException();
			}
		} catch (vException&) {
			throw vFatalException("Wrong material file syntax, \"Pass\" element should have valid params. Error in \"%s\" at line: %i.", 
				fileName.ToConstCharPtr(), mLineInFile);
		}
	}

	vBool vMaterialParser::parseBoolParam(const vString& fileName, const vString& val) {
		vBool bOk = false, bRes;
		bRes = vStringConverter::ToBool(val, &bOk);

		if (!bOk) {
			throw vException();
		}
		cout<<"Bool="<<bRes<<endl;

		return bRes;
	}

	vReal vMaterialParser::parseRealParam(const vString& fileName, const vString& val) {
		vBool bOk = false;
		vReal rRes;
		rRes = vStringConverter::ToReal(val, &bOk);

		if (!bOk) {
			throw vException();
		}
		cout<<"Real="<<rRes<<endl;

		return rRes;
	}

	vColor vMaterialParser::parseLightComponent(const vString& fileName, const vString& val) {
		vBool bOk = false;
		vString noBraces = vStringTokenizer::EraseDelimeter(vStringTokenizer::EraseDelimeter(val, "["), "]");
		vColor color = vStringConverter::ToColor(noBraces, &bOk);

		if (!bOk) {
			throw vException();
		}

		cout<<"Color="<<vStringConverter::ToString(color)<<endl;

		return vColor::Black;
	}

	vReal vMaterialParser::parseSpecularFactor(const vString& fileName, const vString& val) {
		vUInt uiPos = vStringTokenizer::EraseDelimeter(val, " ").Find("],");
		vBool bOk = false;
		vReal vRes;

		if (uiPos != vString::MaxPos && (uiPos + 2) < val.Length()) {
			vRes = vStringConverter::ToReal(val.SubString(uiPos + 2, val.Length()), &bOk);
			cout<<"Fact="<<vRes<<endl;
			if (!bOk) {
				throw vException();
			}
		} else {
			throw vException();
		}
		
		return vRes;
	}

	vTextureUnit::eAddressMode vMaterialParser::parseAddressMode(const vString& fileName, const vString& val) {
		vString valLower = vString(val).ToLowerCase();

		cout<<"AddressMode="<<val<<endl;
		if (vString("repeat").IsEqual(valLower)) {
			return vTextureUnit::VAM_REPEAT;
		} else if (vString("clamp").IsEqual(valLower)) {
			return vTextureUnit::VAM_CLAMP;
		} else if (vString("mirror").IsEqual(valLower)) {
			return vTextureUnit::VAM_MIRROR;
		} else {
			throw vException();
		}
	}

	vTextureUnit::eFilterMode vMaterialParser::parseFilterMode(const vString& fileName, const vString& val) {
		vString valLower = vString(val).ToLowerCase();

		cout<<"FilterMode="<<val<<endl;
		if (vString("none").IsEqual(valLower)) {
			return vTextureUnit::VFM_NONE;
		} else if (vString("bilinear").IsEqual(valLower)) {
			return vTextureUnit::VFM_BILINEAR;
		} else if (vString("trilinear").IsEqual(valLower)) {
			return vTextureUnit::VFM_TRILINEAR;
		} else if (vString("anisotropic").IsEqual(valLower)) {
			return vTextureUnit::VFM_ANISOTROPIC;
		} else {
			throw vException();
		}
	}

	vTextureUnit::eBlendMode vMaterialParser::parseBlendMode(const vString& fileName, const vString& val) {
		vString valLower = vString(val).ToLowerCase();

		cout<<"BlendMode="<<val<<endl;
		if (vString("one").IsEqual(valLower)) {
			return vTextureUnit::VBM_ONE;
		} else if (vString("zero").IsEqual(valLower)) {
			return vTextureUnit::VBM_ZERO;
		} else {
			throw vException();
		}
	}

	vBool vMaterialParser::changeOpenState(eLoadState eState) {
		switch (eState) {
			case VLS_MATERIAL_OPEN_BRACKET:
				mStates.Pop();
				mStates<<VLS_MATERIAL_IN;
				break;
			case VLS_PASS_OPEN_BRACKET:
				mStates.Pop();
				mStates<<VLS_PASS_IN;
				break;
			case VLS_TEXTURE_OPEN_BRACKET:
				mStates.Pop();
				mStates<<VLS_TEXTURE_IN;
				break;
			default:
				return false;
		}

		return true;
	}

	vBool vMaterialParser::changeCloseState(eLoadState eState) {
		switch (eState) {
			case VLS_MATERIAL_IN:
				mStates.Pop();
				mStates<<VLS_DEFAULT;
				break;
			case VLS_PASS_IN:
				mStates.Pop();
				mStates<<VLS_MATERIAL_IN;
				break;
			case VLS_TEXTURE_IN:
				mStates.Pop();
				mStates<<VLS_PASS_IN;
				break;
			default:
				return false;
		}

		return true;
	}
}